package org.teamswift.crow.rbac.cmd;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import org.springframework.context.ApplicationContext;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.teamswift.crow.rbac.annotation.LoginRequiredOnly;
import org.teamswift.crow.rbac.annotation.PermissionAction;
import org.teamswift.crow.rbac.annotation.PublicAction;
import org.teamswift.crow.rbac.annotation.RequestedResource;
import org.teamswift.crow.rbac.common.ICrowPermissionResourceConfigure;
import org.teamswift.crow.rbac.entity.CrowPermissionResource;
import org.teamswift.crow.rbac.enums.CrowResourceActions;
import org.teamswift.crow.rbac.service.CrowPermissionResourceService;
import org.teamswift.crow.rest.common.CrowController;
import org.teamswift.crow.rest.utils.CrowBeanUtils;
import org.teamswift.crow.rest.utils.GenericUtils;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.stream.Collectors;

@ShellComponent(value = "Crow RBAC")
public class CrowRbac {

    private final ObjectMapper objectMapper;

    private final CrowPermissionResourceService crowPermissionResourceService;

    public CrowRbac(ObjectMapper objectMapper, CrowPermissionResourceService crowPermissionResourceService) {
        this.objectMapper = objectMapper;
        this.crowPermissionResourceService = crowPermissionResourceService;
    }

    @ShellMethod(
            group = "Crow"
    )
    public void rbacGeneratePermissionNodes() throws IOException {
        Map<String, CrowPermissionResource> map = getResourcesMap();

        String filePath = String.format(
                "%s/src/main/resources/%s",
                System.getProperty("user.dir"),
                crowPermissionResourceService.getPermissionResourceSaveFile()
        );
        BufferedWriter writer = new BufferedWriter(
                new FileWriter(filePath)
        );

        String str = objectMapper.writeValueAsString(map.values());
        writer.write(str);
        writer.close();

        crowPermissionResourceService.refreshPermissionResources();
    }

    public Map<String, CrowPermissionResource> getResourcesMap() {

        Map<String, CrowPermissionResource> resourcesMap = getDefinedResources();

        Map<RequestMappingInfo, HandlerMethod> requestMap = getAllRequestMapping();

        List<String> editActions = Arrays.asList("PUT", "DELETE", "POST");

        requestMap.forEach((requestMappingInfo, method) -> {

            Class<CrowController<?, ?, ?, ?>> controllerCls;
            Class<?> entityCls = null;

            try {
                controllerCls = (Class<CrowController<?, ?, ?, ?>>) Class.forName(method.getBeanType().getName());
                entityCls = GenericUtils.get(controllerCls, 1);
            } catch (Exception e) {
                return;
            }

            if(entityCls == null || entityCls.isAnnotationPresent(PublicAction.class)) {
                return;
            }

            RequestedResource requestedResource = controllerCls.getAnnotation(RequestedResource.class);
            if(requestedResource == null) {
                RequestMapping requestMapping = controllerCls.getAnnotation(RequestMapping.class);

                if(requestMapping == null) {
                    return;
                }

                String[] patterns = requestMapping.value();
                if(patterns.length == 0) {
                    return;
                }

                String resource = patterns[0];
                if(!resource.contains(":")) {
                    resource = patterns[0].substring(1).replaceAll("/", ":");
                }

                String finalResource = resource;
                requestedResource = new RequestedResource() {
                    @Override
                    public Class<? extends Annotation> annotationType() {
                        return RequestedResource.class;
                    }

                    @Override
                    public String value() {
                        return finalResource;
                    }

                    @Override
                    public String category() {
                        return finalResource.split(":")[0];
                    }

                    @Override
                    public boolean rowLevelPermission() {
                        return false;
                    }
                };
            }

            // 仅需登陆
            if(method.hasMethodAnnotation(LoginRequiredOnly.class)
                    || method.hasMethodAnnotation(PublicAction.class)) {
                return;
            }

            // 请求方法
            List<RequestMethod> requestMethods = new ArrayList<>(
                    requestMappingInfo.getMethodsCondition().getMethods()
            );

            if(Strings.isNullOrEmpty(requestedResource.value()) || requestMethods.isEmpty()) {
                return;
            }

            String requestMethod;
            PermissionAction permissionAction = method.getMethodAnnotation(PermissionAction.class);
            if(permissionAction != null) {
                requestMethod = permissionAction.value();
            } else {
                // 仅取第一条， 不允许存在相同 url pattern 相同方法
                requestMethod = requestMethods.get(0).name().toUpperCase(Locale.ROOT);
            }

            CrowPermissionResource definedResource = resourcesMap.get(requestedResource.value());
            if(definedResource == null){
                definedResource = new CrowPermissionResource();
                definedResource.setResource(requestedResource.value());
                definedResource.setSupportRowManagement(requestedResource.rowLevelPermission());
                definedResource.setCategory(requestedResource.category());
            }

            if(!editActions.contains(requestMethod)) {
                definedResource.addSupportedActions(requestMethod);
            }

            resourcesMap.put(requestedResource.value(), definedResource);
        });

        return resourcesMap;
    }

    public Map<String, CrowPermissionResource> getDefinedResources() {
        Map<String, ICrowPermissionResourceConfigure> configuredBeans = CrowBeanUtils.getBeans(ICrowPermissionResourceConfigure.class);

        List<CrowPermissionResource> definedResourcesRaw = new ArrayList<>();
        configuredBeans.values().forEach(conf -> {
            List<CrowPermissionResource> resources = conf.registerResource();

            if(resources != null && resources.size() > 0) {
                definedResourcesRaw.addAll(resources);
            }
        });

        return definedResourcesRaw.stream().collect(Collectors.toMap(
                CrowPermissionResource::getId,
                item -> item
        ));
    }

    public Map<RequestMappingInfo, HandlerMethod> getAllRequestMapping() {

        ApplicationContext applicationContext = CrowBeanUtils.getApplicationContext();

        AbstractHandlerMethodMapping<RequestMappingInfo> objHandlerMethodMapping =
                (AbstractHandlerMethodMapping<RequestMappingInfo>)applicationContext.getBean("requestMappingHandlerMapping");

        return objHandlerMethodMapping.getHandlerMethods();
    }

}
